package tw.com.hitevision.whiteboard.android.widgets.MeasurementTools;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import tw.com.hitevision.whiteboard.android.R;
import tw.com.hitevision.whiteboard.android.util.TextLayoutUtil;
import tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter;

import static tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementValues.ROTATE_SKIP;
import static tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementValues.ROTATE_UNIT;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.TRIANGLE_PROTRACTOR_RULER;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.TRIANGLE_RULER_30;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.TRIANGLE_RULER_45;

@SuppressLint("ViewConstructor")
public class RulerTriangle extends BasicRuler {
    private Drawable triangleDrawable;
    private float defaultSize;
    private float maxSize;
    private float minSize;
    private boolean reset = true;
    private boolean update = false;
    private PointF[] triangle;
    private float bottomHeight;
    private Path trianglePath;
    private Region triangleRegion;
    private float detectLineRange;
    private Paint detectLinePaint;
    private Region detectLineRegion;
    private TriangleType triangleType;
    private MeasurementAction rulerAction = MeasurementAction.MOVE;
    private float dragX;
    private float dragY;
    private float dragX2;
    private float dragY2;
    private float closeBtnDefaultCenterStart;
    private float closeBtnDefaultCenterTop;
    private float rotateBtnDefaultCenterEnd;
    private float rotateBtnDefaultCenterBottom;
    private PointF closeBtnP = new PointF();
    private Drawable closeBtnDrawable;
    private float buttonSize;
    private PointF rotateBtnP = new PointF();
    private Drawable rotateBtnDrawable;
    private float canvasDegree;
    private float totalDegree;
    private float drawingLineWidth;
    private Path outerTrianglePath;
    private PointF[] outerTriangle;
    private TriangleLine triangleLine;
    private TriangleLine startTriangleLine;
    private List<PointF> outerPathPoints;
    private PointF startP;
    private float distance = 0f;
    private MeasurementToolListener rulerListener;
    private TextPaint measurementRectTextPaint;
    private float measurementRectWidth;
    private float measurementRectHeight;
    private float measurementRectRadius;
    private Paint measurementRectPaint;
    private float cmMeasurement;
    private float scaleRatio = 1f;
    private float realScaleRatio = 1f;
    private float translateX;
    private float translateY;
    private float triangleOffset;
    private PointF motionCenterP = new PointF();
    private String measurementText = "";

    public RulerTriangle(Context context, TriangleType type, MeasurementToolListener listener) {
        super(context);
//        name = type == TriangleType.DEGREE30 ? TRIANGLE_RULER_30 : TRIANGLE_RULER_45;
        if (type == TriangleType.DEGREE30) {
            name = TRIANGLE_RULER_30;
        } else if (type == TriangleType.DEGREE45) {
            name = TRIANGLE_RULER_45;
        } else {
            name = TRIANGLE_PROTRACTOR_RULER;
        }
        this.rulerListener = listener;

        triangleType = type;

        if (triangleType == TriangleType.DEGREE45) {
            triangleDrawable = getResources().getDrawable(R.drawable.ic_img_ruler_triangle_45_svg);
            defaultSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_default_size);
            maxSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_max_size);
            minSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_min_size);
            closeBtnDefaultCenterStart = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_close_btn_center_start_margin_default);
            closeBtnDefaultCenterTop = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_close_btn_center_top_margin_default);
            rotateBtnDefaultCenterEnd = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_close_btn_center_top_margin_default);
            rotateBtnDefaultCenterBottom = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_close_btn_center_bottom_margin_default);
        } else if (triangleType == TriangleType.DEGREE30) {
            triangleDrawable = getResources().getDrawable(R.drawable.ic_img_ruler_triangle_30_maximum);
            defaultSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_30_default_width);
            maxSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_30_max_width);
            minSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_30_min_width);
            closeBtnDefaultCenterStart = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_30_close_btn_center_start_margin_default);
            closeBtnDefaultCenterTop = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_30_close_btn_center_top_margin_default);
            rotateBtnDefaultCenterEnd = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_30_rotate_btn_center_end_margin_default);
            rotateBtnDefaultCenterBottom = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_30_rotate_btn_center_bottom_margin_default);
        } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
            triangleDrawable = getResources().getDrawable(R.drawable.img_set_square_protractor);
            defaultSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_protractor_default_width);
            maxSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_protractor_max_width);
            minSize = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_protractor_min_width);
            closeBtnDefaultCenterStart = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_protractor_close_btn_center_start_margin_default);
            closeBtnDefaultCenterTop = -1 * getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_protractor_close_btn_center_top_margin_default);
            rotateBtnDefaultCenterEnd = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_protractor_rotate_btn_center_end_margin_default);
            rotateBtnDefaultCenterBottom = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_protractor_rotate_btn_center_bottom_margin_default);
        }
        triangleOffset = getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_offset);
        bottomHeight = getResources().getDimensionPixelOffset(R.dimen.bottom_bar_height);
        detectLineRange = getResources().getDimensionPixelOffset(R.dimen.ruler_detect_range);
        triangleRegion = new Region();
        detectLineRegion = new Region();
        detectLinePaint = initStrokePaint();
        detectLinePaint.setStrokeWidth(detectLineRange);
        detectLinePaint.setColor(getResources().getColor(R.color.ruler_detect_stroke_color));
        closeBtnDrawable = getResources().getDrawable(R.drawable.ic_ruler_close);
        buttonSize = getResources().getDimensionPixelOffset(R.dimen.ruler_btn_size);
        rotateBtnDrawable = getResources().getDrawable(R.drawable.ic_ruler_rotate);
        outerTrianglePath = new Path();
        outerPathPoints = new ArrayList<>();
        measurementRectTextPaint = new TextPaint();
        measurementRectTextPaint.setAntiAlias(true);
        measurementRectTextPaint.setColor(getResources().getColor(R.color.hitevision_blue));
        measurementRectTextPaint.setTextSize(getResources().getDimensionPixelOffset(R.dimen.ruler_text_font_size));
        measurementRectWidth = type == TriangleType.TRIANGLE_PROTRACTOR ?
                getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_protractor_measurement_rect_width) :
                getResources().getDimensionPixelOffset(R.dimen.ruler_triangle_measurement_rect_width);
        measurementRectHeight = getResources().getDimensionPixelOffset(R.dimen.ruler_measurement_rect_height);
        measurementRectRadius = getResources().getDimensionPixelOffset(R.dimen.ruler_measurement_rect_radius);
        measurementRectPaint = initStrokePaint();
        measurementRectPaint.setStyle(Paint.Style.FILL);
        measurementRectPaint.setColor(getResources().getColor(R.color.ruler_measurement_background_color));
        cmMeasurement = getResources().getDimensionPixelOffset(R.dimen.ruler_cm_unit_distance) * 10f;
    }

    @Override
    public void loadMeasurementToolRecord(MeasurementToolRecord record) {
        PointF center = record.getCenterF();
        if (center == null) {
            return;
        }

        reset = false;
        initTriangle();

        totalDegree = record.getRotateDegree();
        scaleRatio = record.getScaleRatio();
        realScaleRatio = record.getScaleRatio();

        if (triangleType == TriangleType.DEGREE45) {
            triangle[0].set(center.x - defaultSize / 2f, center.y - defaultSize / 2f);
            triangle[1].set(center.x + defaultSize / 2f, center.y + defaultSize / 2f);
            triangle[2].set(center.x - defaultSize / 2f, center.y + defaultSize / 2f);
        } else if (triangleType == TriangleType.DEGREE30) {
            float sqrt3 = (float) Math.sqrt(3);
            triangle[0].set(center.x - defaultSize / 2f, center.y - defaultSize / sqrt3 / 2f);
            triangle[1].set(center.x + defaultSize / 2f, center.y + defaultSize / sqrt3 / 2f);
            triangle[2].set(center.x - defaultSize / 2f, center.y + defaultSize / sqrt3 / 2f);
        } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
            triangle[0].set(center.x + defaultSize / 2f, center.y);
            triangle[1].set(center.x - defaultSize / 2f, center.y);
            triangle[2].set(center.x, center.y - defaultSize / 2f);
        }

        float[] points = new float[]{triangle[0].x, triangle[0].y, triangle[1].x, triangle[1].y, triangle[2].x, triangle[2].y};
        Matrix matrix = new Matrix();
        matrix.setRotate(totalDegree, center.x, center.y);
        matrix.mapPoints(points);
        matrix.setScale(scaleRatio, scaleRatio, center.x, center.y);
        matrix.mapPoints(points);
        triangle[0].set(points[0], points[1]);
        triangle[1].set(points[2], points[3]);
        triangle[2].set(points[4], points[5]);

        closeBtnDefaultCenterStart *= scaleRatio;
        closeBtnDefaultCenterTop *= scaleRatio;
        rotateBtnDefaultCenterEnd *= scaleRatio;
        rotateBtnDefaultCenterBottom *= scaleRatio;

        update = true;
        updateTrianglePath();
        updateRegion();
        invalidate();
    }

    private PointF getCenter(TriangleType type) {
        return new PointF((triangle[0].x + triangle[1].x) / 2f, (triangle[0].y + triangle[1].y) / 2f);
    }

    @Override
    public MeasurementToolRecord createMeasurementToolRecord() {
        if (triangle == null) {
            return new MeasurementToolRecord(name, getWidth() / 2f, getHeight() / 2f, totalDegree, scaleRatio);
        }
        PointF center = getCenter(triangleType);
        return new MeasurementToolRecord(name, center.x, center.y, totalDegree, scaleRatio);
    }

    public void setDrawingLineWidth(float w) {
        drawingLineWidth = w;
    }

    public boolean rulerOnTouch(View v, MotionEvent event, boolean canDraw) {
        int action = event.getActionMasked();
        float x = event.getX();
        float y = event.getY();
        int intX = (int) event.getX();
        int intY = (int) event.getY();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                boolean contain = false;
                rulerAction = MeasurementAction.MOVE;
                boolean hitDetectLine = detectLineRegion.contains(intX, intY);
                boolean hitRuler = triangleRegion.contains(intX, intY);
                if (hitRuler || hitDetectLine) {
                    contain = true;
                }
                if (contain) {
                    dragX = x;
                    dragY = y;

                    if (hitDetectLine && canDraw) {
                        updateTrianglePath();
                        startP = getDrawingStart(x, y);
                        startTriangleLine = triangleLine;
                        outerPathPoints.clear();
                        outerPathPoints.add(startP);
                        if (isLine(triangleLine)) {
                            outerPathPoints.add(new PointF(startP.x, startP.y));
                        }
                        if (rulerListener != null) {
                            List<PointF> ls = new ArrayList<>(outerPathPoints);
                            rulerListener.onDrawing(ls, false);
                        }
                        rulerAction = MeasurementAction.DRAW;
                    } else if (PointF.length(x - closeBtnP.x, y - closeBtnP.y) <= buttonSize / 2f) {
                        rulerAction = MeasurementAction.CLOSE;
                    } else if (PointF.length(x - rotateBtnP.x, y - rotateBtnP.y) <= buttonSize / 2f) {
                        rulerAction = MeasurementAction.ROTATE;
                    }
                }
                return contain;
            }
            case MotionEvent.ACTION_POINTER_DOWN: {
                if (event.getPointerCount() <= 2 && rulerAction == MeasurementAction.MOVE) {
                    rulerAction = MeasurementAction.MULTI_FINGER;
                    dragX2 = event.getX(event.getActionIndex());
                    dragY2 = event.getY(event.getActionIndex());
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                switch (rulerAction) {
                    case DRAW:{
                        drawLine(x, y);
                        if (rulerListener != null) {
                            List<PointF> ls = new ArrayList<>(outerPathPoints);
                            rulerListener.onDrawing(ls, false);
                        }
                        break;
                    }
                    case MOVE:{
                        move(x - dragX, y - dragY);
                        break;
                    }
                    case ROTATE:{
//                        rotate(x, dragX, y, dragY);
                        PointF center = getCenter(triangleType);
                        motionCenterP.set(center.x, center.y);
                        rotate(x, dragX, y, dragY, center.x, center.x, center.y, center.y);
                        break;
                    }
                    case MULTI_FINGER:{
                        if (event.getPointerCount() >= 2) {
                            float x1 = event.getX(0);
                            float y1 = event.getY(0);
                            float x2 = event.getX(1);
                            float y2 = event.getY(1);
                            scale(dragX, dragY, dragX2, dragY2, x1, y1, x2, y2);
                            dragX2 = x2;
                            dragY2 = y2;
                        } else {
                            rulerAction = MeasurementAction.MOVE;
                        }
                        break;
                    }
                }
                measurementText = getMeasurementText(rulerAction);
                dragX = x;
                dragY = y;
                break;
            }
            case MotionEvent.ACTION_UP: {
                switch (rulerAction) {
                    case DRAW:{
                        if (rulerListener != null) {
                            List<PointF> ls = new ArrayList<>(outerPathPoints);
                            rulerListener.onDrawing(ls, true);
                        }
                        break;
                    }
                    case CLOSE:{
                        close();
                        break;
                    }
                    case ROTATE:{
                        PointF center = getCenter(triangleType);
                        motionCenterP.set(center.x, center.y);
                        afterRotate(x, dragX, y, dragY, center.x, center.x, center.y, center.y);
                        updateRegion();
                    }
                    case MOVE:
                    case MULTI_FINGER:{
                        afterMove(0, 0);
                        afterRotate(dragX, dragX, dragY, dragY, dragX2, dragX2, dragY2, dragY2);
                        updateRegion();
                        break;
                    }
                }
                measurementText = "";
                if (rulerListener != null) {
                    rulerListener.updateRecord(name);
                }
                break;
            }
        }
        return false;
    }

    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (visibility == VISIBLE) {
            update = true;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (reset) {
            reset = false;
            initTriangle();
            updateRegion();
            update = true;
            if (rulerListener != null) {
                rulerListener.updateRecord(name);
            }
        }

        if (update) {
            update = false;

            PointF center = getCenter(triangleType);

            canvas.save();

            if (canvasDegree != 0) {
                canvas.rotate(canvasDegree + getDegreeStopOffset(canvasDegree + totalDegree, ROTATE_SKIP, ROTATE_UNIT),
                        motionCenterP.x, motionCenterP.y);
            }

            if (translateX != 0 || translateY != 0) {
                canvas.translate(translateX, translateY);
            }

            // draw triangle ruler
            float width = 0;
            float height = 0;
            if (triangleType == TriangleType.DEGREE30 || triangleType == TriangleType.DEGREE45) {
                width = PointF.length(triangle[2].x - triangle[1].x, triangle[2].y - triangle[1].y);
                height = PointF.length(triangle[0].x - triangle[2].x, triangle[0].y - triangle[2].y);
            } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
                width = PointF.length(triangle[0].x - triangle[1].x, triangle[0].y - triangle[1].y);
                height = width / 2f;
            }
            float bx = center.x;
            float by = center.y;
            float halfW = width / 2f;
            float halfH = height / 2f;
            canvas.save();
            canvas.rotate(totalDegree, center.x, center.y);
            float sw = halfW / (int) halfW;
            float sh = halfH / (int) halfH;
            if (triangleType == TriangleType.DEGREE30 || triangleType == TriangleType.DEGREE45) {
                triangleDrawable.setBounds((int) bx - (int) halfW, (int) by - (int) halfH, (int) bx + (int) halfW, (int) by + (int) halfH);
                canvas.scale(sw, sh, (int) bx, (int) by);
            } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
                triangleDrawable.setBounds((int) bx - (int) halfW, (int) by - (int) height, (int) bx + (int) halfW, (int) by);
                canvas.scale(sw, sh, (int) bx, (int) (by + halfH));
            }
            float imageOffset = triangleOffset * scaleRatio;
            if (triangleType == TriangleType.DEGREE30) {
                canvas.translate(0, 0.8f * imageOffset);
            } else if (triangleType == TriangleType.DEGREE45) {
                canvas.translate(-imageOffset, 1.4f *imageOffset);
            } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
                canvas.translate(0.5f * imageOffset, 0.8f * imageOffset);
            }
            triangleDrawable.draw(canvas);

            canvas.restore();

            // draw detection line
            canvas.drawPath(trianglePath, detectLinePaint);

            PointF vAB = getUnitVector(triangle[1].x - triangle[0].x, triangle[1].y - triangle[0].y);
            PointF vCB = getUnitVector(triangle[1].x - triangle[2].x, triangle[1].y - triangle[2].y);
            PointF vAC = getUnitVector(triangle[2].x - triangle[0].x, triangle[2].y - triangle[0].y);
            PointF vNAB = getNormalVectorCCW(vAB);

            // draw close btn
            if (triangleType == TriangleType.DEGREE30 || triangleType == TriangleType.DEGREE45) {
                bx = triangle[0].x + vCB.x * closeBtnDefaultCenterStart + vAC.x * closeBtnDefaultCenterTop;
                by = triangle[0].y + vCB.y * closeBtnDefaultCenterStart + vAC.y * closeBtnDefaultCenterTop;
            } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
                bx = triangle[1].x - vAB.x * closeBtnDefaultCenterStart - vNAB.x * closeBtnDefaultCenterTop;
                by = triangle[1].y - vAB.y * closeBtnDefaultCenterStart - vNAB.y * closeBtnDefaultCenterTop;
            }
            float btnHalfSize = buttonSize / 2f;
            float offsetX = (bx - btnHalfSize) - (int) (bx - btnHalfSize);
            float offsetY = (by - btnHalfSize) - (int) (by - btnHalfSize);
            canvas.save();
            canvas.rotate(totalDegree, bx, by);
            closeBtnP.set(bx, by);
            closeBtnDrawable.setBounds((int) (bx - btnHalfSize), (int) (by - btnHalfSize),
                    (int) (bx + btnHalfSize), (int) (by + btnHalfSize));
            canvas.translate(offsetX, offsetY);
            closeBtnDrawable.draw(canvas);
            canvas.restore();

            // draw rotate btn
            if (triangleType == TriangleType.DEGREE30 || triangleType == TriangleType.DEGREE45) {
                bx = triangle[1].x - vCB.x * rotateBtnDefaultCenterEnd - vAC.x * rotateBtnDefaultCenterBottom;
                by = triangle[1].y - vCB.y * rotateBtnDefaultCenterEnd - vAC.y * rotateBtnDefaultCenterBottom;
            } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
                bx = triangle[0].x + vAB.x * rotateBtnDefaultCenterEnd + vNAB.x * rotateBtnDefaultCenterBottom;
                by = triangle[0].y + vAB.y * rotateBtnDefaultCenterEnd + vNAB.y * rotateBtnDefaultCenterBottom;
            }
            offsetX = (bx - btnHalfSize) - (int) (bx - btnHalfSize);
            offsetY = (by - btnHalfSize) - (int) (by - btnHalfSize);
            canvas.save();
            canvas.rotate(totalDegree, bx, by);
            rotateBtnP.set(bx, by);
            rotateBtnDrawable.setBounds((int) (bx - btnHalfSize), (int) (by - btnHalfSize),
                    (int) (bx + btnHalfSize), (int) (by + btnHalfSize));
            canvas.translate(offsetX, offsetY);
            rotateBtnDrawable.draw(canvas);
            canvas.restore();

            // draw distance/degree measurement
            if (measurementText.isEmpty() && triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
                measurementText = getMeasurementText(MeasurementAction.ROTATE);
            }

            float textX = (closeBtnP.x + rotateBtnP.x) / 2f;
            float textY = (closeBtnP.y + rotateBtnP.y) / 2f;
            canvas.save();
            if (triangleType == TriangleType.DEGREE30 || triangleType == TriangleType.DEGREE45) {
                canvas.rotate(totalDegree + (triangleType == TriangleType.DEGREE45 ? 45 : 30), textX, textY);
            } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
                canvas.rotate(totalDegree, textX, textY);
            }
            RectF rectF = new RectF(textX - measurementRectWidth /2f, textY - measurementRectHeight / 2f,
                    textX + measurementRectWidth / 2f, textY + measurementRectHeight / 2f);
            canvas.drawRoundRect(rectF, measurementRectRadius, measurementRectRadius, measurementRectPaint);
            float tw = measurementRectTextPaint.measureText(measurementText);
            float th = TextLayoutUtil.getHeightOfString(measurementRectTextPaint, measurementText);
            canvas.drawText(measurementText, textX - tw / 2f, textY + th / 2f, measurementRectTextPaint);
            canvas.restore();

            canvas.restore();
        }
    }

    private void close() {
        OptionsRulerMenuAdapter.getLister(name).onClick(this);
        if (rulerListener != null) {
            rulerListener.onClose(name);
        }
    }

    private PointF getDrawingStart(float x, float y) {
        PointF vAB = getUnitVector(outerTriangle[1].x - outerTriangle[0].x, outerTriangle[1].y - outerTriangle[0].y);
        PointF vBC = getUnitVector(outerTriangle[2].x - outerTriangle[1].x, outerTriangle[2].y - outerTriangle[1].y);
        PointF vAC = getUnitVector(outerTriangle[2].x - outerTriangle[0].x, outerTriangle[2].y - outerTriangle[0].y);
        PointF nvAB = new PointF(-vAB.y, vAB.x);
        PointF nvBC = new PointF(-vBC.y, vBC.x);
        PointF nvAC = new PointF(-vAC.y, vAC.x);
        float rawDisP2AC = pointToLineRawDistance(x, y, nvAC, outerTriangle[0].x, outerTriangle[0].y);
        float rawDisP2AB = pointToLineRawDistance(x, y, nvAB, outerTriangle[1].x, outerTriangle[1].y);
        float rawDisP2BC = pointToLineRawDistance(x, y, nvBC, outerTriangle[2].x, outerTriangle[2].y);
        float disP2AC = Math.abs(rawDisP2AC);
        float disP2AB = Math.abs(rawDisP2AB);
        float disP2BC = Math.abs(rawDisP2BC);
        float disP2A = PointF.length(x - triangle[0].x, y - triangle[0].y);
        float disP2B = PointF.length(x - triangle[1].x, y - triangle[1].y);
        float disP2C = PointF.length(x - triangle[2].x, y - triangle[2].y);
        float pACx, pACy, pABx, pABy, pBCx, pBCy;

        // AC
        pACx = x - nvAC.x * rawDisP2AC;
        pACy = y - nvAC.y * rawDisP2AC;
        // AB
        pABx = x - nvAB.x * rawDisP2AB;
        pABy = y - nvAB.y * rawDisP2AB;
        // BC
        pBCx = x - nvBC.x * rawDisP2BC;
        pBCy = y - nvBC.y * rawDisP2BC;

        int betweenAC = isPointBetweenPoints(pACx, pACy, outerTriangle[0].x, outerTriangle[0].y, outerTriangle[2].x, outerTriangle[2].y);
        int betweenAB = isPointBetweenPoints(pABx, pABy, outerTriangle[0].x, outerTriangle[0].y, outerTriangle[1].x, outerTriangle[1].y);
        int betweenBC = isPointBetweenPoints(pBCx, pBCy, outerTriangle[1].x, outerTriangle[1].y, outerTriangle[2].x, outerTriangle[2].y);

        /**
         *                   .
        *                .
        * area 4     .
        * .......A
        *        |\   area 3
        * area 1 | \                .
        *        |  \           .
        *        |else      .
        * .......|____\ .
        *        C     B
        * area 5 .area2.    area 6
        *        .     .
        *        .     .
        * **/
        triangleLine = TriangleLine.NONE;

        if (betweenAC == 0 && rawDisP2AC >= 0) { // area 1
            triangleLine = TriangleLine.AC;
        } else if (betweenBC == 0 && rawDisP2BC <= 0) { // area 2
            triangleLine = TriangleLine.BC;
        } else if (betweenAB == 0 && rawDisP2AB <= 0) { // area 3
            triangleLine = TriangleLine.AB;
        } else if (betweenAB < 0 && betweenAC < 0 || disP2A <= detectLineRange / 2f) { // area 4
            triangleLine = TriangleLine.A;
        } else if (betweenAC > 0 && betweenBC > 0 || disP2C <= detectLineRange / 2f) { // area 5
            triangleLine = TriangleLine.C;
        } else if (betweenAB > 0 && betweenBC < 0 || disP2B <= detectLineRange / 2f) { // area 6
            triangleLine = TriangleLine.B;
        } else { // else
            if (disP2AC <= disP2AB && disP2AC <= disP2BC) {
                triangleLine = TriangleLine.AC;
            } else if (disP2AB <= disP2AC && disP2AB <= disP2BC) {
                triangleLine = TriangleLine.AB;
            } else if (disP2BC <= disP2AB && disP2BC <= disP2AC) {
                triangleLine = TriangleLine.BC;
            }
        }

        if (triangleLine == TriangleLine.A) {
            return new PointF(outerTriangle[0].x, outerTriangle[0].y);
        } else if (triangleLine == TriangleLine.B) {
            return new PointF(outerTriangle[1].x, outerTriangle[1].y);
        } else if (triangleLine == TriangleLine.C) {
            return new PointF(outerTriangle[2].x, outerTriangle[2].y);
        } else if (triangleLine == TriangleLine.AC) {
            return new PointF(pACx, pACy);
        } else if (triangleLine == TriangleLine.AB) {
            return new PointF(pABx, pABy);
        } else if (triangleLine == TriangleLine.BC) {
            return new PointF(pBCx, pBCy);
        }

        return new PointF(x, y);
    }

    private PointF getDrawingEnd(float x, float y) {
        if (outerPathPoints.size() < 2) {
            triangleLine = startTriangleLine;
            return startP;
        }
        PointF vAB = getUnitVector(outerTriangle[1].x - outerTriangle[0].x, outerTriangle[1].y - outerTriangle[0].y);
        PointF vBC = getUnitVector(outerTriangle[2].x - outerTriangle[1].x, outerTriangle[2].y - outerTriangle[1].y);
        PointF vAC = getUnitVector(outerTriangle[2].x - outerTriangle[0].x, outerTriangle[2].y - outerTriangle[0].y);
        PointF nvAB = new PointF(-vAB.y, vAB.x);
        PointF nvBC = new PointF(-vBC.y, vBC.x);
        PointF nvAC = new PointF(-vAC.y, vAC.x);
        float rawDisP2AC = pointToLineRawDistance(x, y, nvAC, outerTriangle[0].x, outerTriangle[0].y);
        float rawDisP2AB = pointToLineRawDistance(x, y, nvAB, outerTriangle[1].x, outerTriangle[1].y);
        float rawDisP2BC = pointToLineRawDistance(x, y, nvBC, outerTriangle[2].x, outerTriangle[2].y);
        float pACx, pACy, pABx, pABy, pBCx, pBCy;

        // AC
        pACx = x - nvAC.x * rawDisP2AC;
        pACy = y - nvAC.y * rawDisP2AC;
        // AB
        pABx = x - nvAB.x * rawDisP2AB;
        pABy = y - nvAB.y * rawDisP2AB;
        // BC
        pBCx = x - nvBC.x * rawDisP2BC;
        pBCy = y - nvBC.y * rawDisP2BC;

        TriangleLine startLine = TriangleLine.NONE;
        int size = outerPathPoints.size();
        PointF startP = outerPathPoints.get(0);
        PointF secondLast = outerPathPoints.get(size - 2);

        if (secondLast.equals(outerTriangle[0])) {
            startLine = TriangleLine.A;
        } else if (secondLast.equals(outerTriangle[1])) {
            startLine = TriangleLine.B;
        } else if (secondLast.equals(outerTriangle[2])) {
            startLine = TriangleLine.C;
        }

        PointF newP = null;
        int bewTweenStartEnd = 0;

        if ((startLine == TriangleLine.A && (startTriangleLine == TriangleLine.B || startTriangleLine == TriangleLine.AB)) ||
                (startLine == TriangleLine.B && (startTriangleLine == TriangleLine.A || startTriangleLine == TriangleLine.AB))) {
            // AB
            bewTweenStartEnd = isPointBetweenPoints(pABx, pABy, secondLast.x, secondLast.y, startP.x, startP.y);
            if (bewTweenStartEnd == 0) {
                newP = new PointF(pABx, pABy);
                triangleLine = TriangleLine.AB;
            }
        } else if ((startLine == TriangleLine.C && (startTriangleLine == TriangleLine.B || startTriangleLine == TriangleLine.BC)) ||
                (startLine == TriangleLine.B && (startTriangleLine == TriangleLine.C || startTriangleLine == TriangleLine.BC))) {
            // BC
            bewTweenStartEnd = isPointBetweenPoints(pBCx, pBCy, secondLast.x, secondLast.y, startP.x, startP.y);
            if (bewTweenStartEnd == 0) {
                newP = new PointF(pBCx, pBCy);
                triangleLine = TriangleLine.BC;
            }
        } else if ((startLine == TriangleLine.A && (startTriangleLine == TriangleLine.C || startTriangleLine == TriangleLine.AC)) ||
                (startLine == TriangleLine.C && (startTriangleLine == TriangleLine.A || startTriangleLine == TriangleLine.AC))) {
            // AC
            bewTweenStartEnd = isPointBetweenPoints(pACx, pACy, secondLast.x, secondLast.y, startP.x, startP.y);
            if (bewTweenStartEnd == 0) {
                newP = new PointF(pACx, pACy);
                triangleLine = TriangleLine.AC;
            }
        } else {
            return null;
        }

        if (bewTweenStartEnd != 0) {
            newP = bewTweenStartEnd > 0 ? new PointF(startP.x, startP.y) : new PointF(secondLast.x, secondLast.y);
            triangleLine = bewTweenStartEnd > 0 ? startTriangleLine : startLine;
        }

        return newP;
    }

    private void drawLine(float x, float y) {
        final TriangleLine tempLine = triangleLine;
        int maxPoints = (startTriangleLine == TriangleLine.A || startTriangleLine == TriangleLine.B || startTriangleLine == TriangleLine.C) ? 4 : 5;
        PointF endP = outerPathPoints.size() < maxPoints ? getDrawingStart(x, y) : getDrawingEnd(x, y);

        if (triangleLine == TriangleLine.NONE || endP == null) {
            log("Error when drawline : triangleLine == TriangleLine.NONE ? " + (triangleLine == TriangleLine.NONE));
            return;
        }

        if (tempLine == triangleLine) {
            if (outerPathPoints.size() > 1) {
                outerPathPoints.remove(outerPathPoints.size() - 1);
            }
            addPoint(endP);
        } else {
            boolean isTempPoint = (tempLine == TriangleLine.A || tempLine == TriangleLine.B || tempLine == TriangleLine.C);
            boolean isCurrentPoint = (triangleLine == TriangleLine.A || triangleLine == TriangleLine.B || triangleLine == TriangleLine.C);
            PointF middleP = new PointF();
            if (!isTempPoint && !isCurrentPoint) { // line to line
                if ((tempLine == TriangleLine.AB && triangleLine == TriangleLine.BC) || (triangleLine == TriangleLine.AB && tempLine == TriangleLine.BC)) {
                    middleP.set(outerTriangle[1]);
                } else if ((tempLine == TriangleLine.AB && triangleLine == TriangleLine.AC) || (triangleLine == TriangleLine.AB && tempLine == TriangleLine.AC)) {
                    middleP.set(outerTriangle[0]);
                } else {
                    middleP.set(outerTriangle[2]);
                }
                int size = outerPathPoints.size();
                PointF secondLast = size > 1 ? outerPathPoints.get(size - 2) : null;
                if (secondLast != null && (secondLast.equals(middleP))) {
                    outerPathPoints.remove(outerPathPoints.size() - 1);
                    outerPathPoints.remove(outerPathPoints.size() - 1);
                    addPoint(endP);
                } else {
                    outerPathPoints.remove(outerPathPoints.size() - 1);
                    addPoint(middleP);
                    addPoint(endP);
                }
            } else if (isTempPoint && !isCurrentPoint) { // point to line
                int size = outerPathPoints.size();
                PointF secondLast = size > 1 ? outerPathPoints.get(size - 2) : null;
                if (secondLast == null) {
                    // do nothing
                } else if (secondLast.equals(startP)) {
                    if (startTriangleLine == triangleLine) {
                        outerPathPoints.remove(outerPathPoints.size() - 1);
                    } else {
                        // start is point
                        TriangleLine line = TriangleLine.NONE;
                        if (startTriangleLine == TriangleLine.A && tempLine == TriangleLine.B ||
                                startTriangleLine == TriangleLine.B && tempLine == TriangleLine.A) {
                            line = TriangleLine.AB;
                        } else if (startTriangleLine == TriangleLine.A && tempLine == TriangleLine.C ||
                                startTriangleLine == TriangleLine.C && tempLine == TriangleLine.A) {
                            line = TriangleLine.AC;
                        } else if (startTriangleLine == TriangleLine.B && tempLine == TriangleLine.C ||
                                startTriangleLine == TriangleLine.C && tempLine == TriangleLine.B) {
                            line = TriangleLine.BC;
                        }
                        if (line == triangleLine) {
                            outerPathPoints.remove(outerPathPoints.size() - 1);
                        }
                    }
                } else {
                    PointF next = null;
                    if ((tempLine == TriangleLine.A && triangleLine == TriangleLine.AB) || (tempLine == TriangleLine.C && triangleLine == TriangleLine.BC)) {
                        next = new PointF(outerTriangle[1].x, outerTriangle[1].y);
                    } else if ((tempLine == TriangleLine.B && triangleLine == TriangleLine.AB) || (tempLine == TriangleLine.C && triangleLine == TriangleLine.AC)) {
                        next = new PointF(outerTriangle[0].x, outerTriangle[0].y);
                    } else if ((tempLine == TriangleLine.B && triangleLine == TriangleLine.BC) || (tempLine == TriangleLine.A && triangleLine == TriangleLine.AC)) {
                        next = new PointF(outerTriangle[2].x, outerTriangle[2].y);
                    }
                    if (next != null && next.equals(secondLast)) {
                        outerPathPoints.remove(outerPathPoints.size() - 1);
                    }
                }

                addPoint(endP);
            } else if (!isTempPoint && isCurrentPoint) { // line to point
                int size = outerPathPoints.size();
                PointF secondLast = size > 1 ? outerPathPoints.get(size - 2) : null;
                if (size > 1) {
                    outerPathPoints.remove(outerPathPoints.size() - 1);
                }
                if (secondLast != null && !secondLast.equals(endP)) {
                    addPoint(endP);
                }
            } else { // point to point
                log("point to point" + ", temp : " + tempLine.name() + ", current : " + triangleLine.name() + ", size : " + outerPathPoints.size());
                return;
            }
        }

        distance = calculateDistance(outerPathPoints);

        update = true;
        invalidate();
    }

    private void addPoint(PointF pointF) {
        if (pointF != null &&
                (outerPathPoints.size() == 0 || !outerPathPoints.get(outerPathPoints.size() - 1).equals(pointF))) {
            outerPathPoints.add(pointF);
        }
    }

    private float calculateDistance(List<PointF> points) {
        float d = 0f;
        for (int i = 0; i < points.size() - 1; i++) {
            d += PointF.length(points.get(i + 1).x - points.get(i).x, points.get(i + 1).y - points.get(i).y);
        }

        return d;
    }


    private float calculateDistance(PointF[] points) {
        float d = 0f;
        for (int i = 0; i < points.length; i++) {
            int next = (i + 1) % points.length;
            d += PointF.length(points[i].x - points[next].x, points[i].y - points[next].y);
        }

        return d;
    }

    // if (x1, y1) -- (x0, y0) -- (x2, y2), return 0;
    // if (x0, y0) -- (x1, y1) -- (x2, y2), return -1;
    // if (x1, y1) -- (x2, y2) -- (x0, y0), return 1;
    private int isPointBetweenPoints(float x0, float y0, float x1, float y1, float x2, float y2) {
        float dis01 = PointF.length(x0 - x1, y0 - y1);
        float dis12 = PointF.length(x1 - x2, y1 - y2);

        if ((x1 - x0) * (x2 - x0) + (y1 - y0) * (y2 - y0) <= 0) {
            return 0;
        } else {
            return dis01 > dis12 ? 1 : -1;
        }
    }

    enum TriangleLine {
        AB, AC, BC, A, B, C, NONE
    }

    private void move(float x, float y) {
        float centerY = (triangle[0].y + triangle[1].y + triangle[2].y) / 3f;
        if (centerY + translateY + y > getHeight() - bottomHeight) {
            y = getHeight() - bottomHeight - centerY - translateY;
        }
        translateX += x;
        translateY += y;
        update = true;
        invalidate();
    }

    private void afterMove(float x, float y) {
        move(x, y);
        for (PointF pointF : triangle) {
            pointF.x += translateX;
            pointF.y += translateY;
        }
        translateX = 0;
        translateY = 0;
        updateTrianglePath();
        update = true;
        invalidate();
    }

    private void rotate(float x, float dragX, float y, float dragY, float x2, float dragX2, float y2, float dragY2) {
        PointF v1 = new PointF(x - x2, y - y2);
        PointF v2 = new PointF(dragX - dragX2, dragY - dragY2);
        float arc1 = (float) Math.atan2(v1.x, v1.y);
        float arc2 = (float) Math.atan2(v2.x, v2.y);
        float d = (float) Math.toDegrees(arc2 - arc1);
        canvasDegree += d;

        update = true;
        invalidate();
    }

    private void afterRotate(float x, float dragX, float y, float dragY, float x2, float dragX2, float y2, float dragY2) {
        PointF v1 = new PointF(x - x2, y - y2);
        PointF v2 = new PointF(dragX - dragX2, dragY - dragY2);
        float arc1 = (float) Math.atan2(v1.x, v1.y);
        float arc2 = (float) Math.atan2(v2.x, v2.y);
        float d = (float) Math.toDegrees(arc2 - arc1);
        canvasDegree += d;

        canvasDegree += getDegreeStopOffset(totalDegree + canvasDegree, ROTATE_SKIP, ROTATE_UNIT);

        Matrix matrix = new Matrix();
        float[] p = new float[]{triangle[0].x, triangle[0].y, triangle[1].x, triangle[1].y,
                triangle[2].x, triangle[2].y};
        matrix.setRotate(canvasDegree, motionCenterP.x, motionCenterP.y);
        matrix.mapPoints(p);

        totalDegree += canvasDegree;
        canvasDegree = 0;

        triangle[0].set(p[0], p[1]);
        triangle[1].set(p[2], p[3]);
        triangle[2].set(p[4], p[5]);

        updateTrianglePath();
        update = true;
        invalidate();
    }

    private void scale(float dragX, float dragY, float dragX2, float dragY2, float x1, float y1, float x2, float y2) {
        float offsetX = (((x1 + x2) / 2f) - ((dragX + dragX2) / 2f));
        float offsetY = (((y1 + y2) / 2f) - ((dragY + dragY2) / 2f));
        afterMove(offsetX, offsetY);
        motionCenterP.set((x1 + x2) / 2f, (y1 + y2) / 2f);

        float oldDistance = PointF.length(dragX - dragX2, dragY - dragY2);
        float distance = PointF.length(x1 - x2, y1 - y2);
        final float oldDiameter = triangleType == TriangleType.TRIANGLE_PROTRACTOR ?
                PointF.length(triangle[0].x - triangle[1].x, triangle[0].y - triangle[1].y) :
                PointF.length(triangle[1].x - triangle[2].x, triangle[1].y - triangle[2].y);
        float newDiameter = oldDiameter + (distance - oldDistance);
        if (oldDiameter > 0 && newDiameter > 0) {
            float scale = newDiameter / oldDiameter;
            if (oldDiameter <= minSize && scale <= 1f) {
                return;
            }
            if (oldDiameter >= maxSize && scale >= 1f) {
                return;
            }
            if (scale == 1f) {
                return;
            }
            if (scale * oldDiameter < minSize) {
                scale = minSize / oldDiameter;
            } else if (scale * oldDiameter > maxSize) {
                scale = maxSize / oldDiameter;
            }
            // scaling at 100 percent
            realScaleRatio *= scale;
            if (realScaleRatio < 1.01f && realScaleRatio > 0.99f) {
                scale = 1f / scaleRatio;
            }

            scaleRatio *= scale;

            PointF centerF = getCenter(triangleType);
            Matrix matrix = new Matrix();
            matrix.setScale(scale, scale, centerF.x, centerF.y);
            float[] floats = new float[] {triangle[0].x, triangle[0].y, triangle[1].x, triangle[1].y,
                    triangle[2].x, triangle[2].y};
            matrix.mapPoints(floats);

            triangle[0].set(floats[0], floats[1]);
            triangle[1].set(floats[2], floats[3]);
            triangle[2].set(floats[4], floats[5]);

            closeBtnDefaultCenterStart *= scale;
            closeBtnDefaultCenterTop *= scale;
            rotateBtnDefaultCenterEnd *= scale;
            rotateBtnDefaultCenterBottom *= scale;
        }

        rotate(x1, dragX, y1, dragY, x2, dragX2, y2, dragY2);
        updateTrianglePath();
        update = true;
        invalidate();
    }

    private void initTriangle() {
        float width = getWidth();
        float height = getHeight();
        PointF center = new PointF(width / 2f, height / 2f);

        if (triangle == null) {
            triangle = new PointF[3];
            for (int i = 0; i < triangle.length; i++) {
                triangle[i] = new PointF();
            }
        }

        if (outerTriangle == null) {
            outerTriangle = new PointF[3];
            for (int i = 0; i < outerTriangle.length; i++) {
                outerTriangle[i] = new PointF();
            }
        }

        if (triangleType == TriangleType.DEGREE45) {
            triangle[0].set(center.x - defaultSize / 2f, center.y - defaultSize / 2f);
            triangle[1].set(center.x + defaultSize / 2f, center.y + defaultSize / 2f);
            triangle[2].set(center.x - defaultSize / 2f, center.y + defaultSize / 2f);
        } else if (triangleType == TriangleType.DEGREE30) {
            float sqrt3 = (float) Math.sqrt(3);
            triangle[0].set(center.x - defaultSize / 2f, center.y - defaultSize / sqrt3 / 2f);
            triangle[1].set(center.x + defaultSize / 2f, center.y + defaultSize / sqrt3 / 2f);
            triangle[2].set(center.x - defaultSize / 2f, center.y + defaultSize / sqrt3 / 2f);
        } else if (triangleType == TriangleType.TRIANGLE_PROTRACTOR) {
            triangle[0].set(center.x + defaultSize / 2f, center.y + defaultSize / 4f);
            triangle[1].set(center.x - defaultSize / 2f, center.y + defaultSize / 4f);
            triangle[2].set(center.x, center.y - defaultSize / 4f);
        }

        updateTrianglePath();
    }

    private void updateTrianglePath() {
        if (trianglePath == null) {
            trianglePath = new Path();
        }
        trianglePath.rewind();
        trianglePath.moveTo(triangle[0].x, triangle[0].y);
        trianglePath.lineTo(triangle[1].x, triangle[1].y);
        trianglePath.lineTo(triangle[2].x, triangle[2].y);
        trianglePath.lineTo(triangle[0].x, triangle[0].y);

        // update drawing line angular path
        PointF vAB = getUnitVector(triangle[1].x - triangle[0].x, triangle[1].y - triangle[0].y);
        PointF vBC = getUnitVector(triangle[2].x - triangle[1].x, triangle[2].y - triangle[1].y);
        PointF vCA = getUnitVector(triangle[0].x - triangle[2].x, triangle[0].y - triangle[2].y);
        float halfW = drawingLineWidth / 2f + triangleOffset / 2f;
        float sec45 = (float) Math.sqrt(2);
        float sec30 = 2f / (float) Math.sqrt(3);
        float sec60 = 2f;
        float value1 = triangleType == TriangleType.DEGREE30 ? sec30 : sec45;
        float value2 = triangleType == TriangleType.DEGREE30 ? sec60 : sec45;

        outerTriangle[0].x = triangle[0].x - vAB.x * halfW * value1 + vCA.x * halfW * value1;
        outerTriangle[0].y = triangle[0].y - vAB.y * halfW * value1 + vCA.y * halfW * value1;
        outerTriangle[1].x = triangle[1].x + vAB.x * halfW * value2 - vBC.x * halfW * value2;
        outerTriangle[1].y = triangle[1].y + vAB.y * halfW * value2 - vBC.y * halfW * value2;
        outerTriangle[2].x = triangle[2].x - vCA.x * halfW + vBC.x * halfW;
        outerTriangle[2].y = triangle[2].y - vCA.y * halfW + vBC.y * halfW;

        outerTrianglePath.rewind();
        outerTrianglePath.moveTo(outerTriangle[0].x, outerTriangle[0].y);
        outerTrianglePath.lineTo(outerTriangle[1].x, outerTriangle[1].y);
        outerTrianglePath.lineTo(outerTriangle[2].x, outerTriangle[2].y);
        outerTrianglePath.lineTo(outerTriangle[0].x, outerTriangle[0].y);
    }

    private void updateRegion() {
        // triangle ruler region
        Rect rect = new Rect();
        RectF rectF = new RectF();
        Region clipRegion = new Region();
        trianglePath.computeBounds(rectF, true);
        rectF.roundOut(rect);
        clipRegion.set(rect);
        triangleRegion.setPath(trianglePath, clipRegion);

        /*
           A
           | \
           C - B
         */
        // outer triangle ruler line region
        PointF vAB = getUnitVector(triangle[1].x - triangle[0].x, triangle[1].y - triangle[0].y);
        PointF vBC = getUnitVector(triangle[2].x - triangle[1].x, triangle[2].y - triangle[1].y);
        PointF vCA = getUnitVector(triangle[0].x - triangle[2].x, triangle[0].y - triangle[2].y);
        PointF nvAB = getNormalVectorCCW(vAB);
        PointF nvBC = getNormalVectorCCW(vBC);
        PointF nvCA = getNormalVectorCCW(vCA);
        float halfW = detectLineRange / 2f;

        Path detectPath = new Path();

        Path path = new Path();
        path.moveTo(triangle[0].x + nvAB.x * halfW, triangle[0].y + nvAB.y * halfW);
        path.lineTo(triangle[1].x + nvAB.x * halfW, triangle[1].y + nvAB.y * halfW);
        path.lineTo(triangle[1].x - nvAB.x * halfW, triangle[1].y - nvAB.y * halfW);
        path.lineTo(triangle[0].x - nvAB.x * halfW, triangle[0].y - nvAB.y * halfW);
        path.lineTo(triangle[0].x + nvAB.x * halfW, triangle[0].y + nvAB.y * halfW);
        detectPath.addPath(path);
        detectPath.addCircle(triangle[1].x, triangle[1].y, halfW, Path.Direction.CCW);

        path.rewind();
        path.moveTo(triangle[1].x + nvBC.x * halfW, triangle[1].y + nvBC.y * halfW);
        path.lineTo(triangle[2].x + nvBC.x * halfW, triangle[2].y + nvBC.y * halfW);
        path.lineTo(triangle[2].x - nvBC.x * halfW, triangle[2].y - nvBC.y * halfW);
        path.lineTo(triangle[1].x - nvBC.x * halfW, triangle[1].y - nvBC.y * halfW);
        path.lineTo(triangle[1].x + nvBC.x * halfW, triangle[1].y + nvBC.y * halfW);
        detectPath.addPath(path);
        detectPath.addCircle(triangle[2].x, triangle[2].y, halfW, Path.Direction.CCW);

        path.rewind();
        path.moveTo(triangle[2].x + nvCA.x * halfW, triangle[2].y + nvCA.y * halfW);
        path.lineTo(triangle[0].x + nvCA.x * halfW, triangle[0].y + nvCA.y * halfW);
        path.lineTo(triangle[0].x - nvCA.x * halfW, triangle[0].y - nvCA.y * halfW);
        path.lineTo(triangle[2].x - nvCA.x * halfW, triangle[2].y - nvCA.y * halfW);
        path.lineTo(triangle[2].x + nvCA.x * halfW, triangle[2].y + nvCA.y * halfW);
        detectPath.addPath(path);
        detectPath.addCircle(triangle[0].x, triangle[0].y, halfW, Path.Direction.CCW);

        detectPath.computeBounds(rectF, true);
        rectF.roundOut(rect);
        clipRegion.set(rect);
        detectLineRegion.setPath(detectPath, clipRegion);

    }

    private String getMeasurementText(MeasurementAction action) {
        if (action == MeasurementAction.DRAW) {
            return String.format(Locale.ENGLISH, "%.1f", distance / cmMeasurement / scaleRatio );
        } else if (action == MeasurementAction.ROTATE || Math.abs(canvasDegree) != 0) {
            float degree = -(totalDegree + canvasDegree + getDegreeStopOffset(totalDegree + canvasDegree, ROTATE_SKIP, ROTATE_UNIT));
            float angle = (degree + 360) % 360;
            return String.format(Locale.ENGLISH, "%.1f°", angle);
        } else if (action == MeasurementAction.MULTI_FINGER) {
            return String.format(Locale.ENGLISH, "%d%%", Math.round(scaleRatio * 100f));
        }
        return "";
    }

    public boolean isLine(TriangleLine line) {
        return line == TriangleLine.AB || line == TriangleLine.BC || line == TriangleLine.AC;
    }

    private float pointToLineRawDistance(float x, float y, PointF np, float px, float py) {
        float c = np.x * px + np.y * py;// ax + by = c
        return  (float)((np.x * x + np.y * y - c) / Math.sqrt(np.x * np.x + np.y * np.y));
    }

    public enum TriangleType {
        DEGREE45, DEGREE30, TRIANGLE_PROTRACTOR
    }

    private void log(String s) {
        Log.i(RulerTriangle.class.getSimpleName(), s);
    }
}
